﻿using System;
using System.Collections.Generic;
using Castle.DynamicProxy;
using System.Reflection;
using System.IO;
using ProtoBuf;
using System.Diagnostics;
using System.Xml.Serialization;
using System.Text;
using System.ServiceModel.Description;
using System.ServiceModel;
using javanet.JavaNETCommunicationProxyReference;
using JavaNET.context;
using JavaNET.listener;
using JavaNET.exception;


namespace JavaNET.communication
{
    /// <summary>
    /// author: Markus Holzer
    /// </summary>
    internal class JavaNETCommunicationProxy : IInterceptor
    {
        public JavaNETFacadeContext facadeContext { get; set; }

        public JavaNETCommunicationProxy() { }
        public JavaNETCommunicationProxy(JavaNETFacadeContext facadeContext)
        {
            this.facadeContext = facadeContext;
        }

        public void Intercept(IInvocation invocation)
        {
            JavaNETCommunicationEvent javaNETEvent = new JavaNETCommunicationEvent();
            JavaNETCommunicationListener listener = null;
            try
            {

                JavaNETCommunicationRequest request = new JavaNETCommunicationRequest();

                // listener
                listener = JavaNETCommunicationProxyFactory.createRegisteredListener();

                // get facade, method
                MethodInfo method = invocation.Method;
                javaNETEvent.methodToInvoke = method;
                request.facadeName = method.DeclaringType.Name;
                request.facadeMethod = method.Name;

                // get parameters
                List<JavaNETCommunicationRequest.JavaNETCommunicationRequestParameter> parameters = new List<JavaNETCommunicationRequest.JavaNETCommunicationRequestParameter>();
                object[] args = invocation.Arguments;
                javaNETEvent.parameters = args;
                // first parameter: context
                JavaNETCommunicationRequest.JavaNETCommunicationRequestParameter param =
                        new JavaNETCommunicationRequest.JavaNETCommunicationRequestParameter();
                String xml = serializeParameter(facadeContext);
                param.type = facadeContext.GetType().Name;
                param.value = xml;
                parameters.Add(param);
                foreach (object arg in invocation.Arguments)
                {
                    param = new JavaNETCommunicationRequest.JavaNETCommunicationRequestParameter();
                    xml = serializeParameter(arg);
                    param.type = arg.GetType().Name;
                    param.value = xml;
                    parameters.Add(param);
                }
                request.parameter = parameters;

                // invoke facade (java)
                fireBeforeFacadeInvocationEvent(listener, javaNETEvent);
                JavaNETCommunicationProxyClient proxy = JavaNETCommunicationProxyFactory.getJavaNETCommunicationProxyClient();
                //EndpointAddress ea = new EndpointAddress("sdfsdf");
                //proxy.Endpoint = ea;
                MemoryStream ms = new MemoryStream();
                Serializer.Serialize(ms, request);
                byte[] bytes = proxy.invokeFacade(ms.ToArray());
                ms.Close();
                ms = new MemoryStream(bytes);
                JavaNETCommunicationResponse response = Serializer.Deserialize<JavaNETCommunicationResponse>(ms);

                Object returnObject = null;
                if (response.success)
                {
                    if (!response.returnType.Equals("void"))
                    {
                        returnObject = deserializeParameter(response.returnValue, invocation.Method.ReturnType);
                    }
                    javaNETEvent.returnValue = returnObject;
                    fireAfterFacadeInvocationEvent(listener, javaNETEvent);
                    invocation.ReturnValue = returnObject;
                }
                else
                {
                    JavaNETException exception = new JavaNETException(response.errorCode, response.errorMessage);
                    javaNETEvent.exception = exception;
                    fireOnExceptionEvent(listener, javaNETEvent);
                    throw exception;
                }

            }
            catch (JavaNETException e)
            {
                throw e;
            }
            catch (Exception e)
            {
                JavaNETException javaNETException = new JavaNETException(e.Message, e.InnerException);
                javaNETEvent.exception = javaNETException;
                fireOnExceptionEvent(listener, javaNETEvent);
                throw javaNETException;
            }
        }

        private void fireBeforeFacadeInvocationEvent(JavaNETCommunicationListener listener, JavaNETCommunicationEvent javaNETEvent)
        {
            if (listener != null)
                listener.beforeFacadeInvocationEvent(javaNETEvent);
        }
        private void fireAfterFacadeInvocationEvent(JavaNETCommunicationListener listener, JavaNETCommunicationEvent javaNETEvent)
        {
            if (listener != null)
                listener.afterFacadeInvocationEvent(javaNETEvent);
        }
        private void fireOnExceptionEvent(JavaNETCommunicationListener listener, JavaNETCommunicationEvent javaNETEvent)
        {
            if (listener != null)
                listener.onException(javaNETEvent);
        }

        private String serializeParameter(Object obj)
        {
            Type[] extraType = new Type[1];
            extraType[0] = obj.GetType();

            XmlSerializer mySerializer = null;
            System.IO.MemoryStream t = new System.IO.MemoryStream();
            if (isTypeInstanceOfWrappedParameter(obj.GetType()))
            {
                mySerializer = new XmlSerializer(typeof(ParameterWrapper), extraType);
                ParameterWrapper wrapper = new ParameterWrapper();
                wrapper.wrappedObject = obj;
                mySerializer.Serialize(t, wrapper);
            }
            else
            {
                mySerializer = new XmlSerializer(obj.GetType());
                mySerializer.Serialize(t, obj);
            }


            UTF8Encoding utf = new UTF8Encoding();
            string strbytes = utf.GetString(t.ToArray());
            // REPLACE
            String prefix = "type=\"";
            String suffix = "\"";
            String nameOfType = obj.GetType().Name;
            String searchString = prefix + nameOfType + suffix;
            String newString = searchString.Substring(0, 6) + searchString.Substring(6, 1).ToLower() + searchString.Substring(7);
            Debug.WriteLine("serialized Parameter: " + strbytes);

            strbytes = strbytes.Replace(searchString, newString);
            Debug.WriteLine(strbytes);
            return strbytes;
        }

        private static void writeStringToFile(string xml)
        {
            using (StreamWriter outfile = new StreamWriter("C:/test/param1.xml"))
            {
                outfile.Write(xml);
            }

        }

        private T deserializeParameter<T>(String toDeserialize, Type type)
        {
            Debug.WriteLine("deserializeParameter: \n" + toDeserialize);


            XmlSerializer xmlSerDes = new XmlSerializer(type);
            StringReader sr;

            using (sr = new StringReader(toDeserialize))
            {
                return (T)xmlSerDes.Deserialize(sr);
            }
        }

        private Object deserializeParameter(String toDeserialize, Type type)
        {
            Debug.WriteLine("deserializeParameter: \n" + toDeserialize);


            XmlSerializer xmlSerDes = new XmlSerializer(type);
            StringReader sr;

            using (sr = new StringReader(toDeserialize))
            {
                return xmlSerDes.Deserialize(sr);
            }
        }

        private static bool isTypeInstanceOfWrappedParameter(Type type)
        {
            if (type == typeof(String))
                return true;
            if (type == typeof(long))
                return true;
            if (type == typeof(int))
                return true;
            if (type == typeof(Boolean))
                return true;
            if (type == typeof(DateTime))
                return true;

            return false;
        }
    }


}